#include "lexer.hpp"
#include "preprocessor.hpp"
#include "parser.hpp"
#include "utils.hpp"

vector<Variable> tokenize(string fullsrc, Environment& env, LexerFlags largs, std::istream &source)
{
	Variable tmp;
	vector<Variable> ret;
	int c=0,d=1;
	c=env.getScope();
	string buffer;
#if CURRENT_OS == OS_linux
	if(fullsrc[fullsrc.size()-1]=='\r') {
		fullsrc = fullsrc.substr(0,fullsrc.size()-1);
	}
#endif
	//tab character
	while((fullsrc[0]=='\t') || (fullsrc[0]==' ')) {
		if(fullsrc[0]=='\t') {
			d++;
		}
		fullsrc=fullsrc.substr(1,fullsrc.size()-1);
	}
	if(!(largs & DISABLESCOPE)) {
		if(d>c) {
			for(int i=0;i<(d-c);i++) {
				env.inscope();
			}
		} else if(d<c) {
			for(int i=c;i>d;i--) {
				env.outscope();
			}
		}
	}
	//String is empty set it to null
	if(fullsrc=="") {
		tmp.str="null";
		tmp.type=tFunction;
		ret.push_back(tmp);
		return ret;
	}
	string full = preprocess(fullsrc,source);
	if(full.find_first_not_of(" \t\n\0")==string::npos) {
		full += "null";
	}
	do {
		if( (full[0]==' ') || (full[0]=='\t') || (full[0]=='\n') || (full[0]=='\0') ) {
			//This is whitespace
			full=full.substr(1);
			continue;
		} else if( full[0]==';' ) {
			//Finish the command, and continue
			if(!ret.empty()) {
				parse(ret,env);
				ret.clear();
				if(env.flags & Break_Scope) {
					env.flags &= ~Break_Scope;
					break;
				}
			}
			full = full.substr(1);
			if(full.size()==0) {
				tmp.str="null";
				tmp.type=tFunction;
				ret.push_back(tmp);
				return ret;
			}
			continue;
		}  else if( (full[0]=='\'') ) {
			//This is a string - not escaped
			string s;
			full=full.substr(1);
			unsigned i;
			for(i=0;i<full.size();i++) {
				if(full[i]=='\\') {
					if(i+1 != full.size()) {
						if(full[i+1]=='\'') {
							s += '\'';
							i++;
						} else {
							s += '\\';
						}
					} else {
						s += '\\';
					}
				} else if(full[i]=='\'') {
					full = full.substr(i+1);
					break;
				} else {
					s += full[i];
				}
			}
			tmp = s;
			ret.push_back(tmp);
			if(i==full.size()) {
				full.clear();
			}
		} else if( (full[0]=='"') ) {
			//This is a string
			string s;
			full=full.substr(1);
			unsigned i;
			for(i=0;i<full.size();i++) {
				if(full[i]=='\\') {
					if(i+1 != full.size()) {
						switch(full[++i]) {
						case 'b':
							// Backspace
							s = s.substr(0,s.size()-1);
							break;
						case 'n':
							// Newline
							s += '\n';
							break;
						case 't':
							// Tab
							s += '\t';
							break;
						case 'r':
							// Carriage return
							s += '\r';
							break;
						case '\\':
							// Backslash
							s += '\\';
							break;
						case '"':
							// Doublequote
							s += '"';
							break;
						case '\'':
							// Singlequote
							s += '\'';
							break;
						default:
							// Ignore the backslash
							--i;
							break;
						}
					} else {
						s += '\\';
					}
				} else if(full[i]=='"') {
					full = full.substr(i+1);
					break;
				} else {
					s += full[i];
				}
			}
			tmp = s;
			ret.push_back(tmp);
			if(i==full.size()) {
				full.clear();
			}
		} else if( (full[0]=='`') ) {
			//This is a string - not escaped
			string s;
			full=full.substr(1);
			unsigned i;
			for(i=0;i<full.size();i++) {
				if(full[i]=='\\') {
					if(i+1 != full.size()) {
						if(full[i+1]=='`') {
							s += '`';
							i++;
						} else {
							s += '\\';
						}
					} else {
						s += '\\';
					}
				} else if(full[i]=='`') {
					full = full.substr(i+1);
					break;
				} else {
					s += full[i];
				}
			}
			tmp = s;
			ret.push_back(tmp);
			if(i==full.size()) {
				full.clear();
			}
		} else if( (full[0]=='(') ) {
			string s;
			int bcount = 1;		//Parenthesis count
			unsigned ccount;	//Iterator 
			for(ccount = 1;ccount<full.size();ccount++) {
				//Finds position of matching end brace
				if(full[ccount]=='(') {
					bcount++;
				} else if(full[ccount]==')') {
					bcount--;
				}
				//Trim into contents of parenthesis
				if(bcount <= 0) {
					if(largs & DISABLERECURSION) {
						tmp.str = full.substr(0,ccount);
						tmp.type = tFunction;
					} else {
						//Run contents
						ccount--;
						try {
							tmp = orate(full.substr(1,ccount), env, DISABLESCOPE, source);
						} catch (ErrorType e) {
							if(e==ERROR_RETURNING) {
								tmp = env.getvar("__return__");
							} else {
								throw;
							}
						}
					}
					ret.push_back(tmp);
					full=full.substr(ccount+2);
					break;
				}
			}
			continue;
		} else if( (full[0]=='{') ) {
			string s;
			int bcount = 1;		//Number of curly brackets
			unsigned ccount;	//Iterator
			for(ccount = 1;ccount<full.size();ccount++) {
				if(full[ccount]=='{') {
					bcount++;
				} else if(full[ccount]=='}') {
					bcount--;
				}
				//Get contents of brackets 
				if(bcount <= 0) {
					ccount--;
					tmp=full.substr(1,ccount);
					//set to inline code type
					tmp.type=tInline;
					ret.push_back(tmp);
					s = full.substr(1,ccount);
					full=full.substr(ccount+2,full.size()-1);
					break;
				}
			}
			continue;
		} else if( (full[0]=='[') ) {
			string s;
			int bcount = 1;		//Number of brackets
			unsigned ccount;	//Iterator
			for(ccount = 1;ccount<full.size();ccount++) {
				if(full[ccount]=='[') {
					bcount++;
				} else if(full[ccount]==']') {
					bcount--;
				}
				//Get contents of brackets 
				if(bcount <= 0) {
					ccount--;
					tmp.makearr(0);
					//Tokenize them and assign the new value to the array
					tmp.arr = tokenize(full.substr(1,ccount),env,DISABLESCOPE);
					for(unsigned j=0;j<tmp.arr.size();j++) {
						env.verify(tmp.arr[j]);
					}
					if(tmp.arr[0].type == tFunction && tmp.arr[0].str == "null") {
						tmp.arr.clear();
					}
					ret.push_back(tmp);
					s = full.substr(1,ccount);
					full=full.substr(ccount+2,full.size()-1);
					break;
				}
			}
			continue;
		} else if(full[0]=='-') {
			//If next character is negative
			if(full.size() > 1) {
				if(isDecNumeric(full[1])) {
					full = full.substr(1);
					enum {tDecimal, tOctal, tHex, tBinary} numType = tDecimal;
					bool (*isnum)(char, bool) = isDecNumeric;
					if(full.size() > 2 && full[0] == '0') {
						// It's in a base other than base (9+1)
						if(full[1] == 'x') {
							numType = tHex;
							full = full.substr(2);
							isnum = isHexNumeric;
						} else if(full[1] == 'b') {
							numType = tBinary;
							full = full.substr(2);
							isnum = isBinNumeric;
						} else if(full[1] == 'o') {
							numType = tOctal;
							full = full.substr(2);
							isnum = isOctNumeric;
						}
					}
					//Take number out of string
					stringstream ss;
					bool mantissa = false; // Has there already been a mantissa?
					unsigned i=0;
					for(i=0;i<full.size();i++) {
						if(isnum(full[i],true)) { // Make it only detect a period once...
							if(full[i] == '.') {
								if(!mantissa) {
									ss << full[i];
									mantissa = true;
								} else {
									throw(ERROR_MANTISSA);
									break;
								}
							} else {
								ss << full[i];
							}
						} else {
							break;
						}
					}
					switch(numType) {
					case tHex: {
							tmp.i = strToDouble(ss.str(),16);
							break;
						}
					case tOctal: {
							tmp.i = strToDouble(ss.str(),8);
							break;
						}
					case tBinary: {
							string buf;
							ss >> buf;
							tmp.i = strToDouble(ss.str(),2);
							break;
						}
					case tDecimal:
					default: {
							tmp.i = strToDouble(ss.str(),10);
							break;
						}
					};
					//Make number negative
					tmp = -tmp.i;
					full = full.substr(i);
				} else {
					//Negative is opcode
					stringstream ss;
					unsigned i=0;
					for(i=0;i<full.size();i++) {
						if(isopcode(full[i])) {
							ss << full[i];
						} else {
							break;
						}
					}
					ss >> tmp.str;
					tmp.type = tOpcode;
					full = full.substr(i);
				}
			} else {
				//Nothing after negative-Set as opcode
				full = "";
				tmp.str = "-";
				tmp.type = tOpcode;
			}
			ret.push_back(tmp);
		} else if(isopcode(full[0])) {
			//Take opcode out of string
			stringstream ss;
			unsigned i=0;
			for(i=0;i<full.size();i++) {
				if(isopcode(full[i])) {
					ss << full[i];
				} else {
					break;
				}
			}
			ss >> tmp.str;
			tmp.type = tOpcode;
			ret.push_back(tmp);
			full = full.substr(i);
		} else if( isDecNumeric(full[0]) ) {
			enum {tDecimal, tOctal, tHex, tBinary} numType = tDecimal;
			bool (*isnum)(char, bool) = isDecNumeric;
			if(full.size() > 2 && full[0] == '0') {
				// It's in a base other than base (9+1)
				if(full[1] == 'x') {
					numType = tHex;
					full = full.substr(2);
					isnum = isHexNumeric;
				} else if(full[1] == 'b') {
					numType = tBinary;
					full = full.substr(2);
					isnum = isBinNumeric;
				} else if(full[1] == 'o') {
					numType = tOctal;
					full = full.substr(2);
					isnum = isOctNumeric;
				}
			}
			//Take number out of string
			stringstream ss;
			bool mantissa = false; // Has there already been a mantissa?
			unsigned i=0;
			for(i=0;i<full.size();i++) {
				if(isnum(full[i],true)) { // Make it only detect a period once...
					if(full[i] == '.') {
						if(!mantissa) {
							ss << full[i];
							mantissa = true;
						} else {
							throw(ERROR_MANTISSA);
							break;
						}
					} else {
						ss << full[i];
					}
				} else {
					break;
				}
			}
			switch(numType) {
			case tHex: {
					tmp.i = strToDouble(ss.str(),16);
					break;
				}
			case tOctal: {
					tmp.i = strToDouble(ss.str(),8);
					break;
				}
			case tBinary: {
					string buf;
					ss >> buf;
					tmp.i = strToDouble(ss.str(),2);
					break;
				}
			case tDecimal:
			default: {
					tmp.i = strToDouble(ss.str(),10);
					break;
				}
			};
			tmp = tmp.i;
			full = full.substr(i);
			ret.push_back(tmp);
		} else if( islegalvarnamestarter(full[0]) ) {
			// Is it a variable name?
			stringstream ss;
			unsigned i=0;
			for(i=0;i<full.size();i++) {
				if(islegalvarname(full[i])) {
					ss << full[i];
				} else if(full[i]=='(') {
					if(full[i-1]==':') {
						// It's a valid x:(stuff)
						string tmpstr;
						string contents = full.substr(i);
						int bcount = 1;		//Parenthesis count
						unsigned ccount;	//Iterator 
						for(ccount = 1;ccount<contents.size();ccount++) {
							//Finds position of matching end brace
							if(contents[ccount]=='(') {
								bcount++;
							} else if(contents[ccount]==')') {
								bcount--;
							}
							//Trim into contents of parenthesis
							if(bcount <= 0) {
								if(largs & DISABLERECURSION) {
									tmpstr = contents.substr(0,ccount);
								} else {
									//Run contents
									ccount--;
									orate(contents.substr(1,ccount), env, DISABLESCOPE, source);
									tmpstr=env.getvar("__return__").str;
								}
								full=full.substr(0,i) + tmpstr + contents.substr(ccount+2);
								i+=tmpstr.size()-1;
								break;
							}
						}
						ss << tmpstr;
					} else {
						// It's not a valid x:(stuff)
						// It's probably x_(stuff) or similar
						break;
					}
				} else {
					// End of the token
					break;
				}
			}
			tmp = ss.str();
			tmp.type = tVarname;
			full = full.substr(i);
			ret.push_back(tmp);
		} else {
			// It's a special character
			tmp = full.substr(0,1);
			tmp.type = tSpecial;
			full = full.substr(1);
			ret.push_back(tmp);
		}
		if(env.repl.find(ret.back().str) != env.repl.end()) {
			string tmpstr = ret.back().str;
			ret.pop_back();
			for(unsigned i=0;i<env.repl[tmpstr].size();i++) {
				ret.push_back(env.repl[tmpstr][i]);
			}
		}
	} while( full.size() != 0 );
	return ret;
}

Variable orate(string full,Environment& env, LexerFlags largs, std::istream &source)
{
	vector<Variable> cmd = tokenize(full,env,largs,source);
	parse(cmd,env);
	return env.getvar("__return__");
}
